package aconcagua.measure;

import static aconcagua.measure.UnitsTestResource.*

import org.junit.Before;
import org.junit.Test;

class MeasureTest extends GroovyTestCase {

	private zeroCelsius;
	private zeroFahrenheit;
	private thirtytwoFahrenheit;

	private zeroMeters;
	private oneMeter;
	private tenMeters;
	private thousandMillimeters;
	private oneKilometer;
	private zeroCentimeters;
	private oneCentimeter;

	private zeroPesos;
	private onePeso;
	private fivePesos;
	private tenPesos;
	private twentyPesos;
	private zeroDollars;
	private tenDollars;
	private twentyDollars;
	private tenEuros;

	private oneSecond;
	private twoSecond;
	private threeSeconds;
	private oneMinute;

	@Before
	public void setUp() throws Exception {
		
		super.setUp();

		zeroCelsius = new Measure(0, CELSIUS);
		zeroFahrenheit = new Measure(0, FAHRENHEIT);
		thirtytwoFahrenheit = new Measure(32, FAHRENHEIT);

		zeroMeters = new Measure(0, METER);
		oneMeter = new Measure(1, METER);
		tenMeters = new Measure(10, METER);
		thousandMillimeters = new Measure(1000, MILLIMETER);
		oneKilometer = new Measure(1, KILOMETER);
		zeroCentimeters = new Measure(0, CENTIMETER);
		oneCentimeter = new Measure(1, CENTIMETER);

		zeroPesos = new Measure(0, PESO);
		onePeso = new Measure(1, PESO);
		fivePesos = new Measure(5, PESO);
		tenPesos = new Measure(10, PESO);
		twentyPesos = new Measure(20, PESO);
		zeroDollars = new Measure(0, DOLLAR);
		tenDollars = new Measure(10, DOLLAR);
		twentyDollars = new Measure(20, DOLLAR);
		tenEuros = new Measure(10, EURO);

		oneSecond = new Measure(1, SECOND);
		twoSecond = new Measure(2, SECOND);
		threeSeconds = new Measure(3, SECOND);
		oneMinute = new Measure(1, MINUTE);
	}

	// test accessing
	@Test
	void testAmount() {
		assert oneMeter.amount() == 1;
		assert tenMeters.amount() == 10;
	}

	@Test
	void testBaseUnit() {
		assert oneMeter.baseUnit() == METER;
		assert oneKilometer.baseUnit() == METER;
		assert oneCentimeter.baseUnit() == METER;
	}

	@Test
	void testDenominator() {
		assert tenMeters.denominator() == 1;
	}

	@Test
	void testMeasures() {
		assert oneMeter.measures().size() == 1;
		assert oneMeter.measures().contains(oneMeter);
	}

	@Test
	void testNumerator() {
		assert tenMeters.numerator() == tenMeters;
	}

	@Test
	void testUnit() {
		assert oneMeter.unit() == METER;
		assert tenMeters.unit() == METER;
	}

	@Test
	void testUnitNameOf() {
		assert oneMeter.unitNameOf(METER) == METER.nameForOne();
		assert tenMeters.unitNameOf(METER) == METER.nameForMany();
	}

	// test add
	@Test
	void testAddAssociativity() {
		assert (oneMeter + (oneMeter + oneKilometer)).amount() == ((oneMeter + oneMeter) + oneKilometer).amount();
		assert (oneMeter + (oneMeter + oneKilometer)).unit() == ((oneMeter + oneMeter) + oneKilometer).unit();
	}

	@Test
	void testAddBaseUnit() {
		assert (oneMeter + oneMeter).amount() == 2;
		assert (oneMeter + oneMeter).unit() == METER;
	}

	@Test
	void testAddBaseUnitAndDerivedUnit() {
		assert (oneKilometer + oneMeter).amount() == 1001;
		assert (oneKilometer + oneMeter).unit() == METER;

		assert (zeroCelsius + thirtytwoFahrenheit).amount() == (5463/10);
		assert (zeroCelsius + thirtytwoFahrenheit).unit() == KELVIN;
	}

	@Test
	void testAddCommutativity() {
		assert (oneMeter + oneKilometer).amount() == (oneKilometer + oneMeter).amount();
		assert (oneMeter + oneKilometer).unit() == (oneKilometer + oneMeter).unit();
	}

	@Test
	void testAddDerivedUnit() {
		assert (oneKilometer + oneCentimeter).amount() == (100001/100);
		assert (oneKilometer + oneCentimeter).unit() == METER;
	}

	@Test
	void testAddDifferentBaseUnits() {

		shouldFail (UndefinedMeasureBagAmountException.class,
				{ (tenPesos + twentyDollars).amount() });
		shouldFail (UndefinedMeasureBagUnitException.class,
				{ (tenPesos + twentyDollars).unit() });
		assert (tenPesos + twentyDollars).numberOfMeasures() == 2;

		shouldFail (UndefinedMeasureBagAmountException.class,
				{ (tenPesos + (twentyDollars + tenEuros)).amount() });
		shouldFail (UndefinedMeasureBagAmountException.class,
				{ ((tenPesos + twentyDollars) + tenEuros).amount() });
		assert ((tenPesos + twentyDollars) + tenEuros).numberOfMeasures() == 3;
	}

	@Test
	void testAddDifferentUnit() {
		def a10pesosPlus20Dollars = tenPesos + twentyDollars;

		assert a10pesosPlus20Dollars == (tenPesos + twentyDollars); // Equality
		assert a10pesosPlus20Dollars == (twentyDollars + tenPesos); // Commutativity
		assert a10pesosPlus20Dollars == (tenPesos + (tenDollars + tenDollars)); // Associativity
		assert (tenPesos + (twentyDollars + tenEuros)) == ((tenPesos + twentyDollars) + tenEuros); //Associativity

		a10pesosPlus20Dollars + tenPesos;
		assert a10pesosPlus20Dollars == (tenPesos + twentyDollars); // Immutability
		assert (zeroPesos + twentyDollars) == twentyDollars; // Adding zero
		assert ((tenPesos + twentyDollars) + zeroCentimeters) == (tenPesos + twentyDollars); // Adding zero, should be equal to bag without nothing member

		assert zeroPesos == zeroDollars; // zeros are equals
		assert zeroDollars == zeroPesos; // zeros are equals
		assert (zeroPesos + zeroDollars) == (zeroDollars + zeroPesos); // Adding Zero
	}

	@Test
	void testAddDividedMeasure() {
		assert ((tenMeters + (tenMeters / fivePesos)).atSameBaseUnitAs(METER)) == tenMeters;
		assert ((tenMeters + (tenMeters / fivePesos)).atSameBaseUnitAs(METER / PESO)) == (tenMeters / fivePesos);
	}

	@Test
	void testAddBigDecimalWithMeasure() {
		def number = 10.01g;

		assert (number + tenMeters).atSameBaseUnitAs(METER) == tenMeters;
		assert (number + tenMeters).atSameBaseUnitAs(new NullUnit()) == number;
	}

	@Test
	void testAddBigIntegerWithMeasure() {
		def number = 10g;

		assert (number + tenMeters).atSameBaseUnitAs(METER) == tenMeters;
		assert (number + tenMeters).atSameBaseUnitAs(new NullUnit()) == number;
	}

	@Test
	void testAddByteWithMeasure() {
		def number = 10.01 as byte;

		assert (number + tenMeters).atSameBaseUnitAs(METER) == tenMeters;
		assert (number + tenMeters).atSameBaseUnitAs(new NullUnit()) == number;
	}

	@Test
	void testAddDoubleWithMeasure() {
		def number = 10.01 as double;

		assert (number + tenMeters).atSameBaseUnitAs(METER) == tenMeters;
		assert (number + tenMeters).atSameBaseUnitAs(new NullUnit()) == number;
	}

	@Test
	void testAddFloatWithMeasure() {
		def number = 10.01 as float;

		assert (number + tenMeters).atSameBaseUnitAs(METER) == tenMeters;
		assert (number + tenMeters).atSameBaseUnitAs(new NullUnit()) == number;
	}

	@Test
	void testAddIntegerWithMeasure() {
		def number = 10.01 as int;

		assert (number + tenMeters).atSameBaseUnitAs(METER) == tenMeters;
		assert (number + tenMeters).atSameBaseUnitAs(new NullUnit()) == number;
	}

	@Test
	void testAddLongWithMeasure() {
		def number = 10.01 as long;

		assert (number + tenMeters).atSameBaseUnitAs(METER) == tenMeters;
		assert (number + tenMeters).atSameBaseUnitAs(new NullUnit()) == number;
	}

	@Test
	void testAddShortWithMeasure() {
		def number = 10.01 as short;

		assert (number + tenMeters).atSameBaseUnitAs(METER) == tenMeters;
		assert (number + tenMeters).atSameBaseUnitAs(new NullUnit()) == number;
	}

	@Test
	void testAddMultipliedMeasure() {
		assert (tenMeters + (tenMeters * fivePesos)).atSameBaseUnitAs(METER) == tenMeters;
		assert (tenMeters + (tenMeters * fivePesos)).atSameBaseUnitAs(METER * PESO) == (tenMeters * fivePesos);
	}

	@Test
	void testSimplificationsAddingWithZero() {
		assert (oneMeter + 0) == oneMeter;
		assert (oneMeter + 0).unit() == oneMeter.unit();
	}

	// test comparing
	@Test
	void testEqual() {
		assert oneMeter == thousandMillimeters; // Equal on same system
		assert thousandMillimeters == oneMeter; // Equal on same system

		assert tenPesos == tenPesos; // Equals
		assert tenPesos != twentyPesos; // Equal units, different amount

		assert zeroCelsius != zeroFahrenheit; // Equals on the same type of unit but not equal measures
		assert zeroCelsius == thirtytwoFahrenheit;
	}

	@Test
	void testEqualDifferentBaseUnit() {
		assert tenPesos != tenDollars;
	}

	@Test
	void testEqualNothingWhenRepresentSameEntity() {
		assert zeroDollars == zeroPesos; // Zeros of different units must be equivalent since they represent the same entities
	}

	@Test
	void testEqualNumber() {
		assert oneMeter != 1;
		assert 1 != oneMeter;
	}

	@Test
	void testHashCode() {
		def oneMeterHashCode = oneMeter.hashCode();
		def thousandMillimitersHashCode = thousandMillimeters.hashCode();

		assert oneMeter.hashCode() == oneMeterHashCode;
		assert oneMeter.hashCode() != thousandMillimitersHashCode;
	}

	@Test
	void testNotEqual() {
		assert oneMeter != "hello";
		assert "hello" != oneMeter;
		assert oneMeter != new Object();
		assert new Object() != oneMeter;
	}

	// test converting
	@Test
	void testConvertNothing() {
		assert zeroMeters.convertTo(METER) == zeroMeters;
		assert zeroCentimeters.convertTo(METER) == zeroMeters;
	}

	@Test
	void testConvertTo() {
		assert oneMeter.convertTo(METER) == oneMeter;
		assert oneKilometer.convertTo(METER) == (new Measure(1000, METER));
		assert oneCentimeter.convertTo(METER) == (new Measure(1/100, METER));

		assert oneMeter.convertTo(KILOMETER) == (new Measure(1/1000, KILOMETER));
		assert oneMeter.convertTo(CENTIMETER) == (new Measure(100, CENTIMETER));

		assert oneCentimeter.convertTo(KILOMETER) == (new Measure(1/100000, KILOMETER));
		assert oneKilometer.convertTo(CENTIMETER) == (new Measure(100000, CENTIMETER));
	}

	@Test
	void testConvertToBaseUnit() {
		assert oneMeter.convertToBaseUnit() == oneMeter;
		assert oneKilometer.convertToBaseUnit() == (new Measure(1000,  METER));
	}

	@Test
	void testInvalidConvertTo() {
		shouldFail (CanNotConvertMeasureException.class,
				{ oneMeter.convertTo(PESO) });

		try {
			oneMeter.convertTo(PESO)
		} catch (CanNotConvertMeasureException ex) {
			assert ex.getMessage() == "It is not possible to convert 1 meter to peso";
			assert ex.sourceMeasure() == oneMeter;
			assert ex.targetUnit() == PESO;
		}
	}

	// test devision
	@Test
	void testDivisionByDividedMeasure() {
		assert (oneKilometer / (oneMeter / onePeso)) == new Measure(1000, PESO);
		assert (oneKilometer / (onePeso / oneMeter)) == ((oneKilometer * oneMeter) / onePeso);
	}

	@Test
	void testDivisionByMeasureBaseUnitOverBaseUnit() {
		assert tenMeters / tenMeters == 1;
		assert (tenMeters / tenMeters).amount() == 1;
		assert (tenMeters / tenMeters).unit() == new NullUnit();
		assert tenMeters / oneMeter == 10;
		assert tenMeters / oneSecond == (new Measure(10, METER/SECOND));
		assert tenMeters / twoSecond == (new Measure(5, METER/SECOND));
		assert (tenMeters / oneSecond).numerator() == tenMeters;
		assert (tenMeters / oneSecond).denominator() == oneSecond;
	}

	@Test
	void testDivisionByMeasureBaseUnitOverDerivedUnit() {
		assert oneMeter / oneKilometer == (1/1000);
		assert oneMeter / oneCentimeter == 100;
		assert oneMeter / oneMinute == (new Measure(1/60, METER/SECOND));
	}

	@Test
	void testDivisionByMeasureDerivedUnitOverBaseUnit() {
		assert oneKilometer / oneMeter == 1000;
		assert oneKilometer / tenMeters == 100;
		assert oneKilometer / oneKilometer == 1;
		assert oneKilometer / oneSecond == (new Measure(1000, METER/SECOND));
	}

	@Test
	void testDivisionByNumber() {
		assert (zeroPesos / 1).amount() == 0;
		assert tenPesos / 10 == onePeso;
	}

	@Test
	void testDivisionByZero() {
		shouldFail(Exception, { tenPesos / 0 }); // Division by 0
	}
	
	@Test
	void testDivisionBigDecimalWithMeasure() {
		def number = 10.01g;
		
		assert number / tenPesos == (new NullUnit() / PESO).with(number/10);
	}
	
	@Test
	void testDivisionBigIntegerWithMeasure() {
		def number = 10g;
		
		assert number / tenPesos == (new NullUnit() / PESO).with(number/10);
	}
	
	@Test
	void testDivisionByteWithMeasure() {
		def number = 10.01 as byte;
		
		assert number / tenPesos == (new NullUnit() / PESO).with(number/10);
	}
	
	@Test
	void testDivisionDoubleWithMeasure() {
		def number = 10.01 as double;
		
		assert number / tenPesos == (new NullUnit() / PESO).with(number/10);
	}
	
	@Test
	void testDivisionFloatWithMeasure() {
		def number = 10.01 as float;
		
		assert number / tenPesos == (new NullUnit() / PESO).with(number/10);
	}
	
	@Test
	void testDivisionIntegerWithMeasure() {
		def number = 10.01 as int;
		
		assert number / tenPesos == (new NullUnit() / PESO).with(number/10);
	}
	
	@Test
	void testDivisionLongWithMeasure() {
		def number = 10.01 as long;
		
		assert number / tenPesos == (new NullUnit() / PESO).with(number/10);
	}
	
	@Test
	void testDivisionShortWithMeasure() {
		def number = 10.01 as short;
		
		assert number / tenPesos == (new NullUnit() / PESO).with(number/10);
	}
	
	@Test
	void testDivisionOfBaseUnit() {
		assert (tenMeters / 10).amount() == 1;
		assert (tenMeters / 10).unit() == METER;
	}
	
	@Test
	void testDivisionOfDerivedUnit() {
		assert (oneKilometer / 10).amount() == (1/10);
		assert (oneKilometer / 10).unit() == KILOMETER;
	}
	
	@Test
	void testSimplificationsDividingFromZero() {
		assert (0 / oneMeter) == 0;
	}
	
	// test integer division
	@Test
	void testIntegerDivisionByMeasure() {
		assert (tenMeters.intdiv(tenMeters)) == 10.intdiv(10);
		assert (oneMeter.intdiv(tenMeters)) == 1.intdiv(10);
		assert (tenMeters.intdiv(oneSecond)) == (METER.with(10)/SECOND);
		assert (tenMeters.intdiv(threeSeconds)) == (METER.with(3)/SECOND);
	}
	
	@Test
	void testIntegerDivisionByZero() {
		shouldFail(Exception, {tenPesos.intdiv(0)}); // Division by 0
	}
	
	@Test
	void testIntegerDivisionBigDecimalByMeasure() {
		def number = 10.01g;
		
		assert (number.intdiv(tenPesos)) == (new NullUnit() / PESO).with((number/10) as int);
	}
	
	@Test
	void testIntegerDivisionBigIntegerByMeasure() {
		def number = 10g;
		
		assert (number.intdiv(tenPesos)) == (new NullUnit() / PESO).with((number/10) as int);
	}
	
	@Test
	void testIntegerDivisionByteByMeasure() {
		def number = 10 as byte;
		
		assert (number.intdiv(tenPesos)) == (new NullUnit() / PESO).with((number/10) as int);
	}
	
	@Test
	void testIntegerDivisionDoubleByMeasure() {
		def number = 10 as double;
		
		assert (number.intdiv(tenPesos)) == (new NullUnit() / PESO).with((number/10) as int);
	}
	
	@Test
	void testIntegerDivisionFloatByMeasure() {
		def number = 10 as float;
		
		assert (number.intdiv(tenPesos)) == (new NullUnit() / PESO).with((number/10) as int);
	}
	
	@Test
	void testIntegerDivisionIntegerByMeasure() {
		def number = 10 as int;
		
		assert (number.intdiv(tenPesos)) == (new NullUnit() / PESO).with((number/10) as int);
	}
	
	@Test
	void testIntegerDivisionLongByMeasure() {
		def number = 10 as long;
		
		assert (number.intdiv(tenPesos)) == (new NullUnit() / PESO).with((number/10) as int);
	}
	
	@Test
	void testIntegerDivisionShortByMeasure() {
		def number = 10 as short;
		
		assert (number.intdiv(tenPesos)) == (new NullUnit() / PESO).with((number/10) as int);
	}
	
	// test modulo (division remainder)
	@Test
	void testModuloByMeasure() {
		assert (tenMeters % tenMeters) == METER.with(10%10);
		assert (oneMeter % tenMeters) == METER.with(1%10);
		assert (tenMeters % oneSecond) == METER.with(10%1);
		assert (tenMeters % threeSeconds) == METER.with(10%3);
	}
	
	@Test
	void testModuloByZero() {
		shouldFail(Exception, {tenPesos % 0}); // Division by 0
	}
	
	@Test
	void testModuloBigDecimalByMeasure() {
		def number = 10.01g;
		
		assert (number % tenPesos) == ((number as double) % 10);
	}
	
	@Test
	void testModuloBigIntegerByMeasure() {
		def number = 10g;
		
		assert (number % tenPesos) == ((number as int) % 10);
	}
	
	@Test
	void testModuloByteByMeasure() {
		def number = 10 as byte;
		
		assert (number % tenPesos) == (number % 10);
	}
	
	@Test
	void testModuloDoubleByMeasure() {
		def number = 10 as double;
		
		assert (number % tenPesos) == (number % 10);
	}
	
	@Test
	void testModuloFloatByMeasure() {
		def number = 10 as float;
		
		assert (number % tenPesos) == (number % 10);
	}
	
	@Test
	void testModuloIntegerByMeasure() {
		def number = 10 as int;
		
		assert (number % tenPesos) == (number % 10);
	}
	
	@Test
	void testModuloLongByMeasure() {
		def number = 10 as long;
		
		assert (number % tenPesos) == (number % 10);
	}
	
	@Test
	void testModuloShortByMeasure() {
		def number = 10 as short;
		
		assert (number % tenPesos) == (number % 10);
	}
	
	// test interval related protocol
	@Test
	void testTo() {
		assert (oneMeter.to(tenMeters)).start() == oneMeter;
		assert (oneMeter.to(tenMeters)).stop() == tenMeters;
		assert (oneMeter.to(tenMeters)).step() == oneMeter;

		assert (tenMeters.to(oneKilometer)).step() == oneMeter;
		assert (oneCentimeter.to(oneKilometer)).step() == oneCentimeter;
	}
	
	@Test
	void testToAt() {
		def coll = tenMeters.to(tenMeters * 2);
		for (int i = 0; i < coll.size(); i++) {
			assert coll.at(i).amount() == (10 + i);
		}
	}
	
	@Test
	void testToAtByNegative() {
		def coll = (tenMeters * 2).to(tenMeters, oneMeter.negate());
		for (int i = 0; i < coll.size(); i++) {
			assert coll.at(i).amount() == (20 - i);
		}
	}
	
	@Test
	void testToBy() {
		assert (oneMeter.to(tenMeters, oneCentimeter)).start() == oneMeter;
		assert (oneMeter.to(tenMeters, oneCentimeter)).stop() == tenMeters;
		assert (oneMeter.to(tenMeters, oneCentimeter)).step() == oneCentimeter;
	}
	
	@Test
	void testToByDo() {
		def value = 1;
		(oneMeter.to(tenMeters, oneMeter)).perform({ measure ->
			assert value == measure.amount();
			value++;
		});
	}
	
	@Test
	void testToCollect() {
		def coll = (oneMeter.to(tenMeters)).collect({ measure -> measure.amount() });
		coll.inject(1, { value, amount ->
			assert value == amount;
			value = value + 1;
		});
	}
	
	@Test
	void testToFirst() {
		assert (oneMeter.to(tenMeters)).first() == oneMeter;
		assert (tenMeters.to(oneMeter, oneMeter.negate())).first() == tenMeters;
	}
	
	@Test
	void testToLast() {
		assert (oneMeter.to(tenMeters)).last() == tenMeters;
		assert (tenMeters.to(oneMeter, oneMeter.negate())).last() == oneMeter;
	}
	
	@Test
	void testToSize() {
		assert (oneCentimeter.to(oneKilometer)).size() == (100 * 1000);
	}
	
	// test magnitude protocol
	@Test
	void testBetween() {
		assert tenMeters.between(oneMeter, tenMeters);
		assert tenMeters.between(tenMeters, oneKilometer);
	}
	
	@Test
	void testBetweenInclusive() {
		assert tenMeters.between(InclusionStrategy.INCLUSIVE, oneMeter, tenMeters);
		assert tenMeters.between(InclusionStrategy.INCLUSIVE, tenMeters, oneKilometer);
	}
	
	@Test
	void testBetweenInclusiveRight() {
		assert oneMeter.between(InclusionStrategy.INCLUSIVE_RIGHT, zeroMeters, oneMeter);
		assert !oneMeter.between(InclusionStrategy.INCLUSIVE_RIGHT, oneMeter, tenMeters);
	}
	
	@Test
	void testBetweenInclusiveLeft() {
		assert tenMeters.between(InclusionStrategy.INCLUSIVE_LEFT, oneMeter, new Measure(11, METER));
		assert !tenMeters.between(InclusionStrategy.INCLUSIVE_LEFT, oneMeter, tenMeters);
	}
	
	@Test
	void testBetweenNotInclusive() {
		assert tenMeters.between(InclusionStrategy.NOT_INCLUSIVE, oneMeter, oneKilometer);
		assert !tenMeters.between(InclusionStrategy.NOT_INCLUSIVE, oneMeter, tenMeters);
		assert !tenMeters.between(InclusionStrategy.NOT_INCLUSIVE, tenMeters, oneKilometer);
	}
	
	@Test
	void testBetweenNotInclusiveRight() {
		assert tenMeters.between(InclusionStrategy.NOT_INCLUSIVE_RIGHT, oneMeter, new Measure(11, METER));
		assert !tenMeters.between(InclusionStrategy.NOT_INCLUSIVE_RIGHT, oneMeter, tenMeters);
	}
	
	@Test
	void testBetweenNotInclusiveLeft() {
		assert oneMeter.between(InclusionStrategy.NOT_INCLUSIVE_LEFT, zeroMeters, oneMeter);
		assert !oneMeter.between(InclusionStrategy.NOT_INCLUSIVE_LEFT, oneMeter, tenMeters);
	}
	
	@Test
	void testGreaterThan() {
		assert tenMeters > oneMeter;
		assert oneKilometer > oneMeter;
		assert !(oneCentimeter > oneMeter);
		assert !(oneMeter > oneMeter);
		assert !(thousandMillimeters > oneMeter);
		assert oneMeter > 0;
	}
	
	@Test
	void testGreaterOrEqualsThan() {
		assert tenMeters >= oneMeter;
		assert !(oneMeter >= tenMeters);
		assert tenMeters >= tenMeters;
		assert oneMeter >= thousandMillimeters;
		assert oneMeter >= 0;
	}
	
	@Test
	void testLessThan() {
		assert oneMeter < tenMeters;
		assert oneMeter < oneKilometer;
		assert !(oneMeter < oneCentimeter);
		assert !(oneMeter < oneMeter);
		assert !(oneMeter < thousandMillimeters);
		assert !(oneMeter < 0);
	}
	
	@Test
	void testLessOrEqualsThan() {
		assert oneMeter <= tenMeters;
		assert !(tenMeters <= oneMeter);
		assert oneMeter <= oneMeter;
		assert oneMeter <= thousandMillimeters;
		assert !(oneMeter < 0);
	}
	
	@Test
	void testInvalidBetween() {
		try {
			tenMeters.between(oneMeter, onePeso)
		} catch (CanNotConvertMeasureException ex) {
			assert ex.sourceMeasure() == tenMeters;
			assert ex.operationName() == 'convertTo';
			assert ex.targetUnit() == PESO;
		};

		try {
			tenMeters.between(onePeso, oneMeter);
		} catch (CanNotConvertMeasureException ex) {
			assert ex.sourceMeasure() == tenMeters;
			assert ex.targetUnit() == PESO;
		};
	}
	
	@Test
	void testInvalidBetweenInclusive() {
		try {
			tenMeters.between(InclusionStrategy.INCLUSIVE, oneMeter, onePeso)
		} catch (CanNotConvertMeasureException ex) {
			assert ex.sourceMeasure() == tenMeters;
			assert ex.operationName() == 'convertTo';
			assert ex.targetUnit() == PESO;
		};

		try {
			tenMeters.between(InclusionStrategy.INCLUSIVE, onePeso, oneMeter);
		} catch (CanNotConvertMeasureException ex) {
			assert ex.sourceMeasure() == tenMeters;
			assert ex.targetUnit() == PESO;
		};
	}
	
	@Test
	void testInvalidBetweenInclusiveRight() {
		try {
			tenMeters.between(InclusionStrategy.INCLUSIVE_RIGHT, oneMeter, onePeso)
		} catch (CanNotConvertMeasureException ex) {
			assert ex.sourceMeasure() == tenMeters;
			assert ex.operationName() == 'convertTo';
			assert ex.targetUnit() == PESO;
		};

		try {
			tenMeters.between(InclusionStrategy.INCLUSIVE_RIGHT, onePeso, oneMeter);
		} catch (CanNotConvertMeasureException ex) {
			assert ex.sourceMeasure() == tenMeters;
			assert ex.targetUnit() == PESO;
		};
	}
	
	@Test
	void testInvalidBetweenInclusiveLeft() {
		try {
			tenMeters.between(InclusionStrategy.INCLUSIVE_LEFT, oneMeter, onePeso)
		} catch (CanNotConvertMeasureException ex) {
			assert ex.sourceMeasure() == tenMeters;
			assert ex.operationName() == 'convertTo';
			assert ex.targetUnit() == PESO;
		};

		try {
			tenMeters.between(InclusionStrategy.INCLUSIVE_LEFT, onePeso, oneMeter);
		} catch (CanNotConvertMeasureException ex) {
			assert ex.sourceMeasure() == tenMeters;
			assert ex.targetUnit() == PESO;
		};
	}
	
	@Test
	void testInvalidBetweenNotInclusive() {
		try {
			tenMeters.between(InclusionStrategy.NOT_INCLUSIVE, oneMeter, onePeso)
		} catch (CanNotConvertMeasureException ex) {
			assert ex.sourceMeasure() == tenMeters;
			assert ex.operationName() == 'convertTo';
			assert ex.targetUnit() == PESO;
		};

		try {
			tenMeters.between(InclusionStrategy.NOT_INCLUSIVE, onePeso, oneMeter);
		} catch (CanNotConvertMeasureException ex) {
			assert ex.sourceMeasure() == tenMeters;
			assert ex.targetUnit() == PESO;
		};
	}
	
	@Test
	void testInvalidBetweenNotInclusiveRight() {
		try {
			tenMeters.between(InclusionStrategy.NOT_INCLUSIVE_RIGHT, oneMeter, onePeso)
		} catch (CanNotConvertMeasureException ex) {
			assert ex.sourceMeasure() == tenMeters;
			assert ex.operationName() == 'convertTo';
			assert ex.targetUnit() == PESO;
		};

		try {
			tenMeters.between(InclusionStrategy.NOT_INCLUSIVE_RIGHT, onePeso, oneMeter);
		} catch (CanNotConvertMeasureException ex) {
			assert ex.sourceMeasure() == tenMeters;
			assert ex.targetUnit() == PESO;
		};
	}
	
	@Test
	void testInvalidBetweenNotInclusiveLeft() {
		try {
			tenMeters.between(InclusionStrategy.NOT_INCLUSIVE_LEFT, oneMeter, onePeso)
		} catch (CanNotConvertMeasureException ex) {
			assert ex.sourceMeasure() == tenMeters;
			assert ex.operationName() == 'convertTo';
			assert ex.targetUnit() == PESO;
		};

		try {
			tenMeters.between(InclusionStrategy.NOT_INCLUSIVE_LEFT, onePeso, oneMeter);
		} catch (CanNotConvertMeasureException ex) {
			assert ex.sourceMeasure() == tenMeters;
			assert ex.targetUnit() == PESO;
		};
	}
	
	@Test
	void testInvalidGreaterThan() {
		try {
			oneMeter > onePeso;
		} catch (CanNotConvertMeasureException ex) {
			assert ex.sourceMeasure() == oneMeter;
			assert ex.targetUnit() == PESO;
		};
	}
	
	@Test
	void testInvalidGreaterOrEqualsThan() {
		try {
			oneMeter >= onePeso;
		} catch (CanNotConvertMeasureException ex) {
			assert ex.sourceMeasure() == oneMeter;
			assert ex.targetUnit() == PESO;
		};
	}
	
	@Test
	void testInvalidLessThan() {
		try {
			oneMeter < onePeso;
		} catch (CanNotConvertMeasureException ex) {
			assert ex.sourceMeasure() == oneMeter;
			assert ex.targetUnit() == PESO;
		};
	}
	
	@Test
	void testInvalidLessOrEqualsThan() {
		try {
			oneMeter <= onePeso;
		} catch (CanNotConvertMeasureException ex) {
			assert ex.sourceMeasure() == oneMeter;
			assert ex.targetUnit() == PESO;
		};
	}
	
	@Test
	void testMax() {
		assert (tenMeters.max(oneMeter)) == tenMeters;
		assert (oneMeter.max(tenMeters)) == tenMeters;
		assert (tenMeters.max(oneKilometer)) == oneKilometer;
		assert (oneKilometer.max(oneMeter)) == oneKilometer;
		assert (oneCentimeter.max(oneKilometer)) == oneKilometer;
		assert (oneKilometer.max(oneCentimeter)) == oneKilometer;
	}
	
	@Test
	void testMin() {
		assert (tenMeters.min(oneMeter)) == oneMeter;
		assert (oneMeter.min(tenMeters)) == oneMeter;
		assert (tenMeters.min(oneKilometer)) == tenMeters;
		assert (oneKilometer.min(oneMeter)) == oneMeter;
		assert (oneCentimeter.min(oneKilometer)) == oneCentimeter;
		assert (oneKilometer.min(oneCentimeter)) == oneCentimeter;
	}
	
	@Test
	void testInvalidMax() {
		try {
			tenMeters.max(onePeso);
		} catch (CanNotConvertMeasureException ex) {
			assert ex.sourceMeasure() == tenMeters;
			assert ex.targetUnit() == PESO;
		};
	
		shouldFail(CanNotConvertMeasureException.class, {tenMeters.max(1)});
	}
	
	@Test
	void testInvalidMin() {
		try {
			tenMeters.min(onePeso);
		} catch (CanNotConvertMeasureException ex) {
			assert ex.sourceMeasure() == tenMeters;
			assert ex.targetUnit() == PESO;
		};
	
		shouldFail(CanNotConvertMeasureException.class, {tenMeters.min(1)});
	}
	
	// test multiplication
	@Test
	void testMultiplicationOfBaseUnit() {
		assert (tenMeters * 2).amount() == 20;
		assert (tenMeters * 2).unit() == METER;
	}
	
	@Test
	void testMultiplicationOfDerivedUnit() {
		assert (oneKilometer * 2).amount() == 2;
		assert (oneKilometer * 2).unit() == KILOMETER;
	}
	
	@Test
	void testMultiplyByDividedMeasure() {
		assert (onePeso * (tenPesos / oneMeter)).amount() == 10;
		assert (onePeso * (tenPesos / oneMeter)).unit() == (PESO * PESO / METER);
	
		assert (onePeso * (tenMeters / onePeso)) == tenMeters;
	}
	
	@Test
	void testMultiplyByMeasure() {
		assert (onePeso * tenPesos).amount() == 10;
		assert (onePeso * tenPesos).unit() == (PESO * PESO);
	}
	
	@Test
	void testMultiplyByMeasureAssociativity() {
		assert ((onePeso * tenPesos) * tenPesos) == (onePeso * (tenPesos * tenPesos));
	}
	
	@Test
	void testMultiplyByMultipliedMeasure() {
		assert (onePeso * tenPesos * tenPesos).amount() == 100;
		assert (onePeso * tenPesos * tenPesos).unit() == (PESO * PESO * PESO);
	}
	
	@Test
	void testMultiplyByNumber() {
		assert (onePeso * 10) == tenPesos;
		assert (onePeso * 0) == zeroPesos;
		assert (onePeso * 0).isNothing();
		assert (onePeso * 0).amount() == 0;
		assert (onePeso * 0) == zeroDollars;
	}
	
	@Test
	void testMultiplyBigDecimalWithMeasure() {
		def number = 10.01g;
		
		assert number * tenPesos == (new NullUnit() * PESO).with(number * 10);
	}
	
	@Test
	void testMultiplyBigIntegerWithMeasure() {
		def number = 10g;
		
		assert number * tenPesos == (new NullUnit() * PESO).with(number * 10);
	}
	
	@Test
	void testMultiplyByteWithMeasure() {
		def number = 10.01 as byte;
		
		assert number * tenPesos == (new NullUnit() * PESO).with(number * 10);
	}
	
	@Test
	void testMultiplyDoubleWithMeasure() {
		def number = 10.01 as double;
		
		assert number * tenPesos == (new NullUnit() * PESO).with(number * 10);
	}
	
	@Test
	void testMultiplyFloatWithMeasure() {
		def number = 10.01 as float;
		
		assert number * tenPesos == (new NullUnit() * PESO).with(number * 10);
	}
	
	@Test
	void testMultiplyIntegerWithMeasure() {
		def number = 10.01 as int;
		
		assert number * tenPesos == (new NullUnit() * PESO).with(number * 10);
	}
	
	@Test
	void testMultiplyLongWithMeasure() {
		def number = 10.01 as long;
		
		assert number * tenPesos == (new NullUnit() * PESO).with(number * 10);
	}
	
	@Test
	void testMultiplyShortWithMeasure() {
		def number = 10.01 as short;
		
		assert number * tenPesos == (new NullUnit() * PESO).with(number * 10);
	}
	
	//test printing
	@Test
	void testPrintingForOne() {
		assert onePeso.toString() == "1 peso";
		assert oneMeter.toString() == "1 meter";
	}
	
	@Test
	void testPrintingForMany() {
		assert tenPesos.toString() == "10 pesos";
		assert tenDollars.toString() == "10 dollars";
		assert zeroCelsius.toString() == "0 celsius";
	}
	
	// test subtract
	@Test
	void testSubtractAssociativity() {
		assert (oneKilometer - oneMeter - oneMeter).amount() == ((oneKilometer - oneMeter) - oneMeter).amount();
		assert (oneKilometer - oneMeter - oneMeter).unit() == ((oneKilometer - oneMeter) - oneMeter).unit();
	}

	@Test
	void testSubtractBaseUnit() {
		assert (oneMeter - oneMeter).amount() == 0;
		assert (oneMeter - oneMeter).unit() == METER;
	}

	@Test
	void testSubtractBaseUnitAndDerivedUnit() {
		assert (oneKilometer - oneMeter).amount() == 999;
		assert (oneKilometer - oneMeter).unit() == METER;

		assert (zeroCelsius - thirtytwoFahrenheit).amount() == (5107444444416/10000000000);
		assert (zeroCelsius - thirtytwoFahrenheit).unit() == KELVIN;
	}

	@Test
	void testSubtractCommutativity() {
		assert (oneMeter - oneKilometer).amount() != (oneKilometer - oneMeter).amount();
		assert (oneMeter - oneKilometer).unit() == (oneKilometer - oneMeter).unit();
	}

	@Test
	void testSubtractDerivedUnit() {
		assert (oneKilometer - oneCentimeter).amount() == (99999/100);
		assert (oneKilometer - oneCentimeter).unit() == METER;
	}

	@Test
	void testSubtractDifferentBaseUnits() {

		shouldFail (UndefinedMeasureBagAmountException.class,
				{ (tenPesos - twentyDollars).amount() });
		shouldFail (UndefinedMeasureBagUnitException.class,
				{ (tenPesos - twentyDollars).unit() });
		assert (tenPesos - twentyDollars).numberOfMeasures() == 2;

		shouldFail (UndefinedMeasureBagAmountException.class,
				{ (tenPesos - (twentyDollars - tenEuros)).amount() });
		shouldFail (UndefinedMeasureBagAmountException.class,
				{ ((tenPesos - twentyDollars) - tenEuros).amount() });
		assert ((tenPesos - twentyDollars) - tenEuros).numberOfMeasures() == 3;
	}

	@Test
	void testSubtractDifferentUnit() {
		def a10pesosMinus20Dollars = tenPesos - twentyDollars;

		assert a10pesosMinus20Dollars == (tenPesos - twentyDollars); // Equality
		assert a10pesosMinus20Dollars != (twentyDollars - tenPesos); // Commutativity
		assert a10pesosMinus20Dollars == (tenPesos - (tenDollars + tenDollars)); // Associativity
		assert (tenPesos - twentyDollars - tenEuros) == ((tenPesos - twentyDollars) - tenEuros); //Associativity

		a10pesosMinus20Dollars - tenPesos;
		assert a10pesosMinus20Dollars == (tenPesos - twentyDollars); // Immutability
		assert (zeroPesos - twentyDollars) == twentyDollars.negate(); // Subtracting zero
		assert ((tenPesos - twentyDollars) - zeroCentimeters) == (tenPesos - twentyDollars); // Subtracting zero, should be equal to bag without nothing member

		assert zeroPesos == zeroDollars; // zeros are equals
		assert zeroDollars == zeroPesos; // zeros are equals
		assert (zeroPesos - zeroDollars) == (zeroDollars - zeroPesos); // Subtracting Zero
	}

	@Test
	void testSubtractDividedMeasure() {
		assert ((tenMeters - (tenMeters / fivePesos)).atSameBaseUnitAs(METER)) == tenMeters;
		assert ((tenMeters - (tenMeters / fivePesos)).atSameBaseUnitAs(METER / PESO)) == (tenMeters.negate() / fivePesos);
	}

	@Test
	void testSubtractBigDecimalWithMeasure() {
		def number = 10.01g;

		assert (number - tenMeters).atSameBaseUnitAs(METER) == tenMeters.negate();
		assert (number - tenMeters).atSameBaseUnitAs(new NullUnit()) == number;
	}

	@Test
	void testSubtractBigIntegerWithMeasure() {
		def number = 10g;

		assert (number - tenMeters).atSameBaseUnitAs(METER) == tenMeters.negate();
		assert (number - tenMeters).atSameBaseUnitAs(new NullUnit()) == number;
	}

	@Test
	void testSubtractByteWithMeasure() {
		def number = 10.01 as byte;

		assert (number - tenMeters).atSameBaseUnitAs(METER) == tenMeters.negate();
		assert (number - tenMeters).atSameBaseUnitAs(new NullUnit()) == number;
	}

	@Test
	void testSubtractDoubleWithMeasure() {
		def number = 10.01 as double;

		assert (number - tenMeters).atSameBaseUnitAs(METER) == tenMeters.negate();
		assert (number - tenMeters).atSameBaseUnitAs(new NullUnit()) == number;
	}

	@Test
	void testSubtractFloatWithMeasure() {
		def number = 10.01 as float;

		assert (number - tenMeters).atSameBaseUnitAs(METER) == tenMeters.negate();
		assert (number - tenMeters).atSameBaseUnitAs(new NullUnit()) == number;
	}

	@Test
	void testSubtractIntegerWithMeasure() {
		def number = 10.01 as int;

		assert (number - tenMeters).atSameBaseUnitAs(METER) == tenMeters.negate();
		assert (number - tenMeters).atSameBaseUnitAs(new NullUnit()) == number;
	}

	@Test
	void testSubtractLongWithMeasure() {
		def number = 10.01 as long;

		assert (number - tenMeters).atSameBaseUnitAs(METER) == tenMeters.negate();
		assert (number - tenMeters).atSameBaseUnitAs(new NullUnit()) == number;
	}

	@Test
	void testSubtractShortWithMeasure() {
		def number = 10.01 as short;

		assert (number - tenMeters).atSameBaseUnitAs(METER) == tenMeters.negate();
		assert (number - tenMeters).atSameBaseUnitAs(new NullUnit()) == number;
	}

	@Test
	void testSubtractMultipliedMeasure() {
		assert (tenMeters - (tenMeters * fivePesos)).atSameBaseUnitAs(METER) == tenMeters;
		assert (tenMeters - (tenMeters * fivePesos)).atSameBaseUnitAs(METER * PESO) == (tenMeters.negate() * fivePesos);
	}
	
	@Test
	void testIsNothing() {
		assert zeroMeters.isNothing();
		assert zeroCentimeters.isNothing();
		
		assert (new Measure(0, KELVIN)).isNothing();
		assert (new Measure((-5463/20), CELSIUS)).isNothing();
	}
	
	@Test
	void testSameDomainAs() {
		assert oneMeter.sameDomainAs(oneMeter);
		assert oneMeter.sameDomainAs(oneKilometer);
		assert oneKilometer.sameDomainAs(oneMeter);
		assert !oneMeter.sameDomainAs(onePeso);
		assert !onePeso.sameDomainAs(oneMeter);
	}
	
	@Test
	void testTrunc(){
		assert (METER.with(1.05)).trunc() == METER.with(1);
		assert (CENTIMETER.with(1.05)).trunc() == CENTIMETER.with(1);
		assert ((PESO * METER).with(1.05)).trunc() == (PESO * METER).with(1);
		assert ((PESO / METER).with(1.05)).trunc() == (PESO / METER).with(1);
	}
}
